/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    Stars.exe
    FILE:         CmdForceDlg.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Operational Command Dialog (Order of Battle Tab)
*/

#include "MemDebug.h"
#include "CmdForceDlg.h"
#include "CmdMsgDlg.h"
#include "CmpnScreen.h"
#include "Starshatter.h"
#include "Campaign.h"
#include "Combatant.h"
#include "CombatAssignment.h"
#include "CombatGroup.h"
#include "CombatUnit.h"
#include "CombatZone.h"
#include "Ship.h"
#include "ShipDesign.h"
#include "Player.h"
#include "Weapon.h"

#include "Game.h"
#include "DataLoader.h"
#include "Button.h"
#include "ComboBox.h"
#include "ListBox.h"
#include "Slider.h"
#include "Video.h"
#include "Keyboard.h"
#include "Mouse.h"
#include "ParseUtil.h"
#include "FormatUtil.h"

// +--------------------------------------------------------------------+
// DECLARE MAPPING FUNCTIONS:

DEF_MAP_CLIENT(CmdForceDlg, OnMode);
DEF_MAP_CLIENT(CmdForceDlg, OnSave);
DEF_MAP_CLIENT(CmdForceDlg, OnExit);
DEF_MAP_CLIENT(CmdForceDlg, OnForces);
DEF_MAP_CLIENT(CmdForceDlg, OnCombat);
DEF_MAP_CLIENT(CmdForceDlg, OnTransfer);

// +--------------------------------------------------------------------+

CmdForceDlg::CmdForceDlg(Screen* s, FormDef& def, CmpnScreen* mgr)
    : FormWindow(s, 0, 0, s->Width(), s->Height()), manager(mgr), CmdDlg(mgr),
      cmb_forces(0), lst_combat(0), lst_desc(0),
      stars(0), campaign(0), current_group(0), current_unit(0),
      btn_transfer(0)
{
    stars    = Starshatter::GetInstance();
    campaign = Campaign::GetCampaign();

    Init(def);
}

CmdForceDlg::~CmdForceDlg()
{
}

// +--------------------------------------------------------------------+

void
CmdForceDlg::RegisterControls()
{
    cmb_forces     = (ComboBox*) FindControl(400);
    lst_combat     = (ListBox*)  FindControl(401);
    lst_desc       = (ListBox*)  FindControl(402);
    btn_transfer   = (Button*)   FindControl(403);

    RegisterCmdControls(this);

    if (btn_save)
    REGISTER_CLIENT(EID_CLICK,  btn_save,    CmdForceDlg, OnSave);

    if (btn_exit)
    REGISTER_CLIENT(EID_CLICK,  btn_exit,    CmdForceDlg, OnExit);

    for (int i = 0; i < 5; i++) {
        if (btn_mode[i])
        REGISTER_CLIENT(EID_CLICK,  btn_mode[i], CmdForceDlg, OnMode);
    }

    if (cmb_forces)
    REGISTER_CLIENT(EID_SELECT, cmb_forces,  CmdForceDlg, OnForces);

    if (lst_combat) {
        lst_combat->AddColumn("datatype", 0);
        REGISTER_CLIENT(EID_SELECT, lst_combat,  CmdForceDlg, OnCombat);
    }

    if (btn_transfer) {
        btn_transfer->SetEnabled(false);
        REGISTER_CLIENT(EID_CLICK, btn_transfer, CmdForceDlg, OnTransfer);
    }
}

// +--------------------------------------------------------------------+

void
CmdForceDlg::ExecFrame()
{
    CmdDlg::ExecFrame();
}

// +--------------------------------------------------------------------+

void
CmdForceDlg::Show()
{
    mode = MODE_FORCES;

    FormWindow::Show();
    ShowCmdDlg();

    cmb_forces->ClearItems();

    campaign = Campaign::GetCampaign();

    if (campaign) {
        List<Combatant>& combatants = campaign->GetCombatants();

        if (combatants.size() > 0) {
            for (int i = 0; i < combatants.size(); i++) {
                if (IsVisible(combatants[i])) {
                    cmb_forces->AddItem(combatants[i]->Name());
                }
            }

            cmb_forces->SetSelection(0);
            Combatant* c = combatants[0];
            ShowCombatant(c);
        }
    }
}

// +--------------------------------------------------------------------+

bool
CmdForceDlg::IsVisible(Combatant* c)
{
    int nvis = 0;

    if (c) {
        CombatGroup* force = c->GetForce();

        if (force) {
            List<CombatGroup>& groups = force->GetComponents();
            for (int i = 0; i < groups.size(); i++) {
                CombatGroup* g = groups[i];

                if (g->Type()       < CombatGroup::CIVILIAN &&
                        g->CountUnits() > 0                     && 
                        g->IntelLevel() >= Intel::KNOWN)

                nvis++;
            }
        }
    }

    return nvis > 0;
}

// +--------------------------------------------------------------------+

static char pipe_stack[32];
static bool blank_line = false;

void
CmdForceDlg::ShowCombatant(Combatant* c)
{
    if (!lst_combat || !c) return;

    lst_combat->ClearItems();
    ZeroMemory(pipe_stack, 32);

    CombatGroup* force = c->GetForce();

    if (force) {
        List<CombatGroup>& groups = force->GetComponents();
        for (int i = 0; i < groups.size(); i++) {
            CombatGroup* g = groups[i];
            if (g->Type() < CombatGroup::CIVILIAN && g->CountUnits() > 0)
            AddCombatGroup(g);
        }
    }

    current_group = 0;
    current_unit  = 0;

    if (lst_desc)     lst_desc->ClearItems();
    if (btn_transfer) btn_transfer->SetEnabled(false);
}

void
CmdForceDlg::AddCombatGroup(CombatGroup* grp, bool last_child)
{
    if (!lst_combat || !grp || grp->IntelLevel() < Intel::KNOWN) return;

    char prefix[4];

    if (!grp->GetParent() || grp->GetParent()->Type() == CombatGroup::FORCE) {
        if (!grp->IsExpanded()) {
            prefix[0] = Font::PIPE_PLUS;
            prefix[1] = 0;
        }
        else {
            prefix[0] = Font::PIPE_MINUS;
            prefix[1] = 0;
        }
    }
    else {
        prefix[0] = last_child ? Font::PIPE_LL : Font::PIPE_LT;
        prefix[1] = Font::PIPE_HORZ;
        prefix[2] = 0;
        prefix[3] = 0;

        if (grp->GetLiveComponents().size() > 0 || grp->GetUnits().size() > 0) {
            if (grp->IsExpanded())
            prefix[1] = Font::PIPE_MINUS;
            else
            prefix[1] = Font::PIPE_PLUS;
        }
    }

    int index = lst_combat->AddItemWithData(
    Text(pipe_stack) + 
    Text(prefix) + 
    grp->GetDescription(), 
    (DWORD) grp);
    lst_combat->SetItemData(index-1, 1, 0);
    blank_line = false;

    int stacklen = strlen(pipe_stack);

    if (!grp->GetParent() || grp->GetParent()->Type() == CombatGroup::FORCE)
    ; // no stack after first entry
    else if (prefix[0] == Font::PIPE_LT)
    pipe_stack[stacklen++] = Font::PIPE_VERT;
    else
    pipe_stack[stacklen++] = Font::PIPE_NBSP;
    pipe_stack[stacklen] = 0;

    if (grp->IsExpanded() && grp->GetUnits().size() > 0) {
        prefix[0] = (grp->GetLiveComponents().size() > 0) ? Font::PIPE_VERT : Font::PIPE_NBSP;
        prefix[1] = Font::PIPE_NBSP;
        prefix[2] = 0;
        prefix[3] = 0;

        ListIter<CombatUnit>  unit_iter  = grp->GetUnits();
        while (++unit_iter) {
            CombatUnit* unit = unit_iter.value();
            char        info[512];
            int         damage = (int) (100 * unit->GetSustainedDamage() / unit->GetDesign()->integrity);
            
            if (damage < 1 || unit->DeadCount() >= unit->Count()) {
                sprintf_s(info, "%s%s%s", pipe_stack, prefix, unit->GetDescription());
            } else {
                sprintf_s(info, "%s%s%s %d%% damage", pipe_stack, prefix, unit->GetDescription(), damage);
            }

            int index = lst_combat->AddItemWithData(info, (DWORD) unit);

            lst_combat->SetItemData(index-1, 1, 1);
            lst_combat->SetItemColor(index-1, lst_combat->GetForeColor() * 0.65);
        }

        // blank line after last unit in group:
        lst_combat->AddItem(Text(pipe_stack) + Text(prefix));
        blank_line = true;
    }

    if (grp->IsExpanded() && grp->GetLiveComponents().size() > 0) {
        List<CombatGroup>& groups = grp->GetLiveComponents();
        for (int i = 0; i < groups.size(); i++) {
            AddCombatGroup(groups[i], i == groups.size()-1);
        }

        // blank line after last group in group:
        if (!blank_line) {
            lst_combat->AddItem(pipe_stack);
            blank_line = true;
        }
    }

    if (stacklen > 1)
        pipe_stack[stacklen-1] = 0;
    else
        pipe_stack[0] = 0;
}

// +--------------------------------------------------------------------+

void
CmdForceDlg::OnForces(AWEvent* event)
{
    Text name = cmb_forces->GetSelectedItem();

    campaign = Campaign::GetCampaign();

    if (campaign) {
        ListIter<Combatant> iter = campaign->GetCombatants();

        while (++iter) {
            Combatant* c = iter.value();
            
            if (name == c->Name()) {
                ShowCombatant(c);
                break;
            }
        }
    }
}

// +--------------------------------------------------------------------+

struct WepGroup {
    Text name;
    int  count;

    WepGroup() : count(0) { }
};

WepGroup* FindWepGroup(WepGroup* weapons, const char* name)
{
    WepGroup*   group = 0;
    WepGroup*   iter  = weapons;
    int         w     = 0;
    int         first = -1;

    while (!group && w < 8) {
        if (first < 0 && iter->name.length() == 0)
            first = w;

        if (!_stricmp(iter->name, name))
            group = iter;

        iter++;
        w++;
    }

    if (!group && first >= 0) {
        group = weapons + first;
        group->name = name;
    }

    return group;
}

void
CmdForceDlg::OnCombat(AWEvent* event)
{
    static int     old_index  = -1;

    int   top_index   = 0;
    bool  expand      = false;
    DWORD data        = 0;
    DWORD type        = 0;

    current_group     = 0;
    current_unit      = 0;

    if (lst_combat) {
        top_index = lst_combat->GetTopIndex();

        int   index = lst_combat->GetListIndex();
        data  = lst_combat->GetItemData(index);
        type  = lst_combat->GetItemData(index, 1);
        Text  item  = lst_combat->GetItemText(index);
        int   nplus = item.indexOf(Font::PIPE_PLUS);
        int   xplus = -1;
        int   dx    = 10000;

        if (nplus < 0)
        nplus = item.indexOf(Font::PIPE_MINUS);

        if (nplus >= 0 && nplus < 64) {
            char pipe[64];
            strncpy(pipe, item.data(), nplus+1);
            pipe[nplus+1] = 0;

            Rect rect(0, 0, 1000, 20);
            lst_combat->DrawText(pipe, 0, rect, DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_CALCRECT);

            xplus = rect.w;
        }

        if (xplus > 0) {
            dx = Mouse::X() - (lst_combat->GetRect().x + xplus - 16);
        }

        // look for click on plus/minus in pipe
        if (dx >= 0 && dx < 16) {
            if (data && type == 0) {
                CombatGroup* grp = (CombatGroup*) data;
                grp->SetExpanded(!grp->IsExpanded());
                expand = true;

                current_group = grp;
                current_unit  = 0;
            }
        }

        old_index  = index;
    }

    if (campaign && data) {
        if (!expand) {
            lst_desc->ClearItems();

            // combat group
            if (type == 0) {
                CombatGroup* grp = (CombatGroup*) data;

                current_group = grp;
                current_unit  = 0;

                // if has units, show location and assignment
                if (grp->GetUnits().size() > 0) {
                    char txt[64];
                    int  n;

                    n = lst_desc->AddItem("Group:") - 1;
                    lst_desc->SetItemText(n, 1, grp->GetDescription());
                    n = lst_desc->AddItem("Sector:") - 1;
                    lst_desc->SetItemText(n, 1, grp->GetRegion());

                    lst_desc->AddItem(" ");
                    n = lst_desc->AddItem("Sorties:") - 1;

                    if (grp->Sorties() >= 0)
                        sprintf_s(txt, "%d", grp->Sorties());
                    else
                        strcpy_s(txt, "Unavail");

                    lst_desc->SetItemText(n, 1, txt);

                    n = lst_desc->AddItem("Kills:") - 1;

                    if (grp->Kills() >= 0)
                        sprintf_s(txt, "%d", grp->Kills());
                    else
                        strcpy_s(txt, "Unavail");

                    lst_desc->SetItemText(n, 1, txt);

                    n = lst_desc->AddItem("Eff Rating:") - 1;

                    if (grp->Points() >= 0) {
                        if (grp->Sorties() > 0)
                            sprintf_s(txt, "%.1f", (double) grp->Points() / grp->Sorties());
                        else
                            sprintf_s(txt, "%.1f", (double) grp->Points());
                    } else {
                        strcpy_s(txt, "Unavail");
                    }

                    lst_desc->SetItemText(n, 1, txt);
                }

                // else (if high-level) show components
                else {
                    int n;

                    n = lst_desc->AddItem("Group:") - 1;
                    lst_desc->SetItemText(n, 1, grp->GetDescription());

                    ListIter<CombatGroup> c = grp->GetLiveComponents();
                    while (++c) {
                        n = lst_desc->AddItem("-") - 1;
                        lst_desc->SetItemText(n, 1, c->GetDescription());
                    }
                }
            }
            // combat unit
            else {
                CombatUnit* unit = (CombatUnit*) data;
                current_group = unit->GetCombatGroup();
                current_unit  = unit;

                int  n;
                char txt[64];

                n = lst_desc->AddItem("Unit:") - 1;
                lst_desc->SetItemText(n, 1, unit->GetDescription());
                n = lst_desc->AddItem("Sector:") - 1;
                lst_desc->SetItemText(n, 1, unit->GetRegion());

                const ShipDesign* design = unit->GetDesign();
                if (design) {
                    lst_desc->AddItem(" ");
                    n = lst_desc->AddItem("Type:") - 1;
                    lst_desc->SetItemText(n, 1, Ship::ClassName(design->type));
                    n = lst_desc->AddItem("Class:") - 1;
                    lst_desc->SetItemText(n, 1, design->DisplayName());

                    if (design->type < Ship::STATION)
                    FormatNumber(txt, design->radius/2);
                    else
                    FormatNumber(txt, design->radius*2);

                    strcat_s(txt, " m");

                    n = lst_desc->AddItem("Length:") - 1;
                    lst_desc->SetItemText(n, 1, txt);

                    FormatNumber(txt, design->mass);
                    strcat_s(txt, " T");

                    n = lst_desc->AddItem("Mass:") - 1;
                    lst_desc->SetItemText(n, 1, txt);

                    FormatNumber(txt, design->integrity);
                    n = lst_desc->AddItem("Hull:") - 1;
                    lst_desc->SetItemText(n, 1, txt);

                    if (design->weapons.size()) {
                        lst_desc->AddItem(" ");
                        n = lst_desc->AddItem("Weapons:") - 1;

                        WepGroup groups[8];
                        for (int w = 0; w < design->weapons.size(); w++) {
                            Weapon*     gun   = design->weapons[w];
                            WepGroup*   group = FindWepGroup(groups, gun->Group());

                            if (group)
                            group->count++;
                        }

                        for (int g = 0; g < 8; g++) {
                            WepGroup* group = &groups[g];
                            if (group && group->count) {
                                sprintf_s(txt, "%s (%d)", group->name.data(), group->count);
                                if (g > 0) n = lst_desc->AddItem(" ") - 1;
                                    lst_desc->SetItemText(n, 1, txt);
                            }
                        }
                    }
                }
            }
        }

        else {
            List<Combatant>& combatants = campaign->GetCombatants();
            Combatant* c = combatants[0];

            if (cmb_forces) {
                Text name = cmb_forces->GetSelectedItem();

                for (int i = 0; i < combatants.size(); i++) {
                    c = combatants[i];
                    
                    if (name == c->Name()) {
                        break;
                    }
                }
            }

            if (c) {
                ShowCombatant(c);

                lst_combat->ScrollTo(top_index);
                lst_combat->SetSelected(old_index);
            }
        }
    }

    if (btn_transfer && campaign && current_group)
    btn_transfer->SetEnabled( campaign->IsActive() && 
    CanTransfer(current_group) );
}

// +--------------------------------------------------------------------+

bool
CmdForceDlg::CanTransfer(CombatGroup* grp)
{
    if (!grp || !campaign)
    return false;

    if (grp->Type() < CombatGroup::WING)
    return false;

    if (grp->Type() > CombatGroup::CARRIER_GROUP)
    return false;

    if (grp->Type() == CombatGroup::FLEET ||
            grp->Type() == CombatGroup::LCA_SQUADRON)
    return false;

    CombatGroup* player_group = campaign->GetPlayerGroup();
    if (player_group->GetIFF() != grp->GetIFF())
    return false;

    return true;
}


// +--------------------------------------------------------------------+

void
CmdForceDlg::OnSave(AWEvent* event)
{
    CmdDlg::OnSave(event);
}

void
CmdForceDlg::OnExit(AWEvent* event)
{
    CmdDlg::OnExit(event);
}

void
CmdForceDlg::OnMode(AWEvent* event)
{
    CmdDlg::OnMode(event);
}


// +--------------------------------------------------------------------+

void
CmdForceDlg::OnTransfer(AWEvent* event)
{
    if (campaign && current_group) {

        // check player rank/responsibility:
        Player* player    = Player::GetCurrentPlayer();
        int     cmd_class = Ship::FIGHTER;

        switch (current_group->Type()) {
        case CombatGroup::WING:
        case CombatGroup::INTERCEPT_SQUADRON:
        case CombatGroup::FIGHTER_SQUADRON:
            cmd_class = Ship::FIGHTER;
            break;

        case CombatGroup::ATTACK_SQUADRON:
            cmd_class = Ship::ATTACK;
            break;

        case CombatGroup::LCA_SQUADRON:
            cmd_class = Ship::LCA;
            break;

        case CombatGroup::DESTROYER_SQUADRON:
            cmd_class = Ship::DESTROYER;
            break;

        case CombatGroup::BATTLE_GROUP:
            cmd_class = Ship::CRUISER;
            break;

        case CombatGroup::CARRIER_GROUP:
        case CombatGroup::FLEET:
            cmd_class = Ship::CARRIER;
            break;
        }

        char transfer_info[512];

        if (player->CanCommand(cmd_class)) {
            if (current_unit) {
                campaign->SetPlayerUnit(current_unit);

                sprintf_s(transfer_info, "Your transfer request has been approved, %s %s.  You are now assigned to the %s.  Good luck.\n\nFleet Admiral A. Evars FORCOM\nCommanding",
                    Player::RankName(player->Rank()),
                    player->Name().data(),
                    current_unit->GetDescription());
            }
            else {
                campaign->SetPlayerGroup(current_group);

                sprintf_s(transfer_info, "Your transfer request has been approved, %s %s.  You are now assigned to the %s.  Good luck.\n\nFleet Admiral A. Evars FORCOM\nCommanding",
                    Player::RankName(player->Rank()),
                    player->Name().data(),
                    current_group->GetDescription());
            }

            Button::PlaySound(Button::SND_ACCEPT);

            CmdMsgDlg* msgdlg = manager->GetCmdMsgDlg();
            msgdlg->Title()->SetText("Transfer Approved");
            msgdlg->Message()->SetText(transfer_info);
            msgdlg->Message()->SetTextAlign(DT_LEFT);

            manager->ShowCmdMsgDlg();
        }

        else {
            Button::PlaySound(Button::SND_REJECT);

            sprintf_s(transfer_info, "Your transfer request has been denied, %s %s.  The %s requires a command rank of %s.  Please return to your unit and your duties.\n\nFleet Admiral A. Evars FORCOM\nCommanding",
                Player::RankName(player->Rank()),
                player->Name().data(),
                current_group->GetDescription(),
                Player::RankName(Player::CommandRankRequired(cmd_class)));

            CmdMsgDlg* msgdlg = manager->GetCmdMsgDlg();
            msgdlg->Title()->SetText("Transfer Denied");
            msgdlg->Message()->SetText(transfer_info);
            msgdlg->Message()->SetTextAlign(DT_LEFT);

            manager->ShowCmdMsgDlg();
        }
    }
}
